package compound.repository

import annotations.Queryable
import compound.repository.entries.Compound
import exception.DatabaseError
import exception.EntryNotFoundException
import org.springframework.beans.factory.InitializingBean
import types.Hit
import util.TypeCastUtil

/**
 * simple service to lookup data from the database
 */
class LookupService implements InitializingBean {

  boolean transactional = true

  def grailsApplication
  def setting

  void afterPropertiesSet() { this.setting = grailsApplication.config.setting }

/**
 * can return 0 - n
 */
  @Queryable(name = "smiles")
  Collection<Compound> lookupBySmile(def value, Map params = [:]) {
    executeQuery("Select a from compound.repository.entries.Compound a, compound.repository.entries.Smiles b where a.id = b.compound.id and b.code = ? order by a.id", value, params)

  }

  Long countlookupBySmile(def value, Map params = [:]) {
    return executeQueryCount("Select count(distinct a.id) from compound.repository.entries.Compound a, compound.repository.entries.Smiles b where a.id = b.compound.id and b.code = ?", value)

  }

/**
 * can return 0 - n
 */
  @Queryable(name = "kegg")
  Collection<Compound> lookupByKegg(def value, Map params = [:]) {
    executeQuery("Select a from compound.repository.entries.Compound a, compound.repository.entries.Kegg b where a.id = b.compound.id and b.keggId = ? order by a.id", value, params)
  }


  Long countlookupByKegg(def value, Map params = [:]) {
    return executeQueryCount("Select count(distinct a.id) from compound.repository.entries.Compound a, compound.repository.entries.Kegg b where a.id = b.compound.id and b.keggId = ?", value)
  }

/**
 * can return 0 - n
 */
  @Queryable(name = "cid")
  Collection<Compound> lookupByCID(def value, Map params = [:]) {
    executeQuery("Select a from compound.repository.entries.Compound a, compound.repository.entries.PubchemCompound b where a.id = b.compound.id and b.cid = ? order by a.id", TypeCastUtil.getInstance().castIntoInteger(value), params, false)
  }

  Long countlookupByCID(def value, Map params = [:]) {
    def v = TypeCastUtil.getInstance().castIntoInteger(value)
    return executeQueryCount("Select count(distinct a.id) from compound.repository.entries.Compound a, compound.repository.entries.PubchemCompound b where a.id = b.compound.id and b.cid = ?", v,false)
  }

/**
 * can return 0-n
 */
  @Queryable(name = "sid")
  Collection<Compound> lookupBySID(def value, Map params = [:]) {
    return executeQuery("Select a from compound.repository.entries.Compound a, compound.repository.entries.PubchemSubstance b where a.id = b.compound.id and b.sid = ? order by a.id", TypeCastUtil.getInstance().castIntoInteger(value), params, false)
  }

  Long countlookupBySID(def value, Map params = [:]) {

    def v = TypeCastUtil.getInstance().castIntoInteger(value)
    return executeQueryCount("Select count(distinct a.id) from compound.repository.entries.Compound a, compound.repository.entries.PubchemSubstance b where a.id = b.compound.id and b.sid = ?", v,false)
  }

/**
 * can return 0-n
 */
  @Queryable(name = "cas")
  Collection<Compound> lookupByCas(def value, Map params = [:]) {
    return executeQuery("Select a from compound.repository.entries.Compound a, compound.repository.entries.Cas b where a.id = b.compound.id and b.casNumber = ? order by a.id", value, params)
  }

  Long countlookupByCas(def value, Map params = [:]) {
    return executeQueryCount("Select count(distinct a.id) from compound.repository.entries.Compound a, compound.repository.entries.Cas b where a.id = b.compound.id and b.casNumber = ?", value)

  }

/**
 * can return 0 - 1
 */
  Compound lookupByCompoundId(def value, Map params = [:]) {
    def v = TypeCastUtil.getInstance().castIntoLong(value)
    return Compound.get(v)
  }

/**
 * can return 0 - n
 */
  @Queryable(name = "inchi")
  def lookupByInchi(def value, Map params = [:]) {
    Collection<Compound> result = executeQuery("from compound.repository.entries.Compound a where a.inchi = ? order by a.id", value, params)

    if (result.size() == 1) {
      return result.iterator().next()
    }
    else {
      return result
    }

  }


  Long countlookupByInchi(def value, Map params = [:]) {
    return executeQueryCount("select count(distinct a.id) from compound.repository.entries.Compound a where a.inchi = ?", value)
  }

/**
 * can return 0 - n
 */
  @Queryable(name = "formula")
  Collection<Compound> lookupByFormula(def value, Map params = [:]) {
    return executeQuery("from compound.repository.entries.Compound a where a.formula = ? order by a.id", [value], params)
  }

  Long countlookupByFormula(def value, Map params = [:]) {
    return executeQueryCount("select count(distinct a.id) from compound.repository.entries.Compound a where a.formula = ?", value)

  }

/**
 * can return 0 - n
 */
  @Queryable(name = "mass")
  Collection<Compound> lookupByExactMass(def value, Map params = [:]) {
    return executeQuery("from compound.repository.entries.Compound a where a.exactMolareMass = ? order by a.id", TypeCastUtil.getInstance().castIntoDouble(value), params, false)
  }

  Long countlookupByExactMass(def value, Map params = [:]) {

    def v = TypeCastUtil.getInstance().castIntoDouble(value)
    return executeQueryCount("select count(distinct a.id) from compound.repository.entries.Compound a where a.exactMolareMass = ?", v,false)

  }

/**
 * can return 0 - n
 */
  @Queryable(name = "inchikey")
  def lookupByInchiKey(def value, Map params = [:]) {

    Collection<Compound> result = executeQuery("from compound.repository.entries.Compound a where a.inchiHashKey.completeKey = ? order by a.id", value, params)

    if (result.size() == 1) {
      return result.asList().get(0)
    }
    else if (result.isEmpty()) {
      throw new EntryNotFoundException("no compound found for: ${value}")
    }
    else {
      throw new DatabaseError("database error, there can not be more than 1 compound for an inchikey!, offending inchi: $value")
    }

  }

  Long countlookupByInchiKey(def value, Map params = [:]) {
    return executeQueryCount("select count(distinct a.id) from compound.repository.entries.Compound a where a.inchiHashKey.completeKey = ?", value)
  }

/**
 * can return 0 - n
 */
  @Queryable(name = "skeleton")
  Collection<Compound> lookupByPartialInchiKey(def value, Map params = [:]) {
    return executeQuery("from compound.repository.entries.Compound a where a.inchiHashKey.firstBlock = ? order by a.id", value, params)
  }

  Long countlookupByPartialInchiKey(def value, Map params = [:]) {
    return executeQueryCount("select count(distinct a.id) from compound.repository.entries.Compound a where a.inchiHashKey.firstBlock = ?", value)
  }

  @Queryable(name = "name")
  Collection<Compound> lookupByName(def value, Map params = [:]) {
    def val = TypeCastUtil.getInstance().toLowerCase(value);

    return executeQuery("Select distinct a from compound.repository.entries.Compound a, compound.repository.entries.Synonym b where a.id = b.compound.id and LOWER(b.name) like ? order by a.id", val, params)
  }

/**
 *
 * converts a lucense query string to a like string, or attemps to. It's a rather simple version of doing this
 * @param
 value
 * @return
 */
  String convertToLikeWildCard(def value) {

    value = value.replaceAll("\\*", "%")
    value = value.replaceAll("\\?", "_")

    return value
  }

/**
 * checks if we have wildcards
 * @param value
 * @return
 */
  boolean hasWildCards(String value) {
    if (value.contains("*")) {
      return true
    }

    if (value.contains("?")) {
      return true
    }

    return false
  }

  Long countlookupByName(def value, Map params = [:]) {
    def val = TypeCastUtil.getInstance().toLowerCase(value);

    return executeQueryCount("Select count(distinct a.id) from compound.repository.entries.Compound a, compound.repository.entries.Synonym b where a.id = b.compound.id and LOWER(b.name) like ?", val)

  }


  @Queryable(name = "hmdb")
  Collection<Compound> lookupByHMDB(def value, Map params = [:]) {
    return executeQuery("Select a from compound.repository.entries.Compound a, compound.repository.entries.HMDB b where a.id = b.compound.id and b.hmdbId = ? order by a.id", value, params)
  }

  Long countlookupByHMDB(def value, Map params = [:]) {
    return executeQueryCount("Select count(distinct a.id) from compound.repository.entries.Compound a, compound.repository.entries.HMDB b where a.id = b.compound.id and b.hmdbId = ?", value)
  }

  @Queryable(name = "lipidmap")
  Collection<Compound> lookupByLipidMapId(def value, Map params = [:]) {
    return executeQuery("Select a from compound.repository.entries.Compound a, compound.repository.entries.LipidMap b where a.id = b.compound.id and b.lipidMapId = ? order by a.id", value, params)
  }

  Long countlookupByLipidMapId(def value, Map params = [:]) {
    return executeQueryCount("Select count(distinct a.id) from compound.repository.entries.Compound a, compound.repository.entries.LipidMap b where a.id = b.compound.id and b.lipidMapId = ?", value)
  }


  @Queryable(name = "chebi")
  Collection<Compound> lookupByChebiId(def value, Map params = [:]) {
    return executeQuery("Select a from compound.repository.entries.Compound a, compound.repository.entries.Chebi b where a.id = b.compound.id and b.chebiId = ? order by a.id", value, params)
  }

  Long countlookupByChebiId(def value, Map params = [:]) {
    return executeQueryCount("Select count(distinct a.id) from compound.repository.entries.Compound a, compound.repository.entries.Chebi b where a.id = b.compound.id and b.chebiId = ?", value)

  }

/**
 * looks up the compound for a single hit
 * can return 0 - n
 */
  Collection<Compound> lookupByHit(Hit hit, Map params = [:]) {
    //contains our results
    Collection<Compound> result = new HashSet<Compound>()

    //find out which type our hit is
    switch (hit.type) {
      case hit.INCHI:
        log.debug("detected inchi...")
        result.add(lookupByInchi(hit.value, params))
        break
      case hit.INCHI_KEY:
        log.debug("detected inchi key...")

        result.add(lookupByInchiKey(hit.value, params))
        break
      case hit.CAS:
        log.debug("detected cas...")

        result = lookupByCas(hit.value, params)
        break
      case hit.KEGG:
        log.debug("detected kegg...")

        result = lookupByKegg(hit.value, params)
        break
      case hit.COMPOUND_ID:
        log.debug("detected compound id...")

        result.add(lookupByCompoundId(hit.compoundId))
        break
      case hit.OSCAR:
        log.debug("detected oscar hit...")

        result = lookupByName(hit.value, params)
        break

      default:
        break
    }

    //print our model
    log.debug("model: ${result}")

    //return the result
    return result
  }

/**
 * execuates the actual query against the database and can be slow for collections, since each entry in the collection
 * is queried
 */
  Collection<Compound> executeQuery(String query, def val, def params, boolean wildcards = true) {

    if (val instanceof Collection) {
      Collection<Compound> result = new HashSet<Compound>()

      val.each {String s ->
        Collection<Compound> search = executeQuery(query, s, params, wildcards)

        result.addAll(search)
      }
      return result
    }
    else {

      if (wildcards) {
        val = convertToLikeWildCard(val)
      }
      return Compound.executeQuery(query, [val], params)

    }
  }

/**
 *
 * calculates the actual count for the query and supports collections and wildcards
 * @param
 query
 * @param val
 * @param wild
 ards
 * @return
 */
  long executeQueryCount(String query, def val, boolean wildcards = true) {
    if (val instanceof Collection) {
      long result = 0

      val.each {String s ->
        result = result + executeQueryCount(query, s, wildcards)

      }
      return result
    }
    else {

      if (wildcards) {
        val = convertToLikeWildCard(val)
      }

      return (Long) (Compound.executeQuery(query, [val]))[0]

    }
  }

}
